﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MyopicScheduler
{
    public class TaskSetGenerator
    {
        public int NumSets { get; set; }
        public int NumProcs { get; set; }
        public int NumResources { get; set; }
        public int MinC { get; set; }
        public int MaxC { get; set; }
        public int MaxTime { get; set; }
        public double UseP { get; set; } // Prob(Lock resource in exclusive mode)
        public double ShareP { get; set; } // Prob(Lock resource in shared mode)
        public int DeadlineVariance { get; set; } // Deadline is +[0, this value]
        //public double Laxity { get; set; } // Laxity of deadline: 0 for strict, 1 for 100% beyond SCT

        private TaskSetGenerator()
        {
        }

        // TEST METHOD ONLY
        private void PrintParameters()
        {
            var props = typeof(TaskSetGenerator).GetProperties();

            foreach (var prop in props)
            {
                Console.WriteLine(prop.Name + " = " + prop.GetValue(this, null));
            }
        }

        public static TaskSetGenerator CreateGeneratorFromCLA(string[] args)
        {
            if (args.Length % 2 != 0) // If odd number of args...
            {
                throw new Exception("Invalid command line arguments");
            }
            TaskSetGenerator tsg = new TaskSetGenerator();

            List<string> necessary = new List<string> { "NumSets", "NumProcs", "Time", "MaxC", "MinC" };

            for (int i = 0; i < args.Length; i += 2)
            {
                // Hurray giant switch!
                // Probably very few better ways to do this, though
                switch (args[i])
                {
                    case "-ns":
                    case "--NumSets":
                        tsg.NumSets = int.Parse(args[i + 1]);
                        necessary.Remove("NumSets");
                        break;
                    case "-np":
                    case "--NumProcs":
                        tsg.NumProcs = int.Parse(args[i + 1]);
                        necessary.Remove("NumProcs");
                        break;
                    case "-nr":
                    case "--NumResources":
                        tsg.NumResources = int.Parse(args[i + 1]);
                        break;
                    case "-t":
                    case "--Time":
                        tsg.MaxTime = int.Parse(args[i + 1]);
                        necessary.Remove("Time");
                        break;
                    case "-hc":
                    case "--MaxC":
                        tsg.MaxC = int.Parse(args[i + 1]);
                        necessary.Remove("MaxC");
                        break;
                    case "-lc":
                    case "--MinC":
                        tsg.MinC = int.Parse(args[i + 1]);
                        necessary.Remove("MinC");
                        break;
                    case "-l":
                    case "--DeadlineVariance":
                        tsg.DeadlineVariance = int.Parse(args[i + 1]);
                        break;
                    case "-sp":
                    case "--ShareP":
                        tsg.ShareP = double.Parse(args[i + 1]);
                        break;
                    case "-up":
                    case "--UseP":
                        tsg.UseP = double.Parse(args[i + 1]);
                        break;
                    default:
                        // Ignore extra parameters
                        //throw new Exception("Invalid command line argument: " + args[i]);
                        break;
                }
            }

            if (necessary.Count != 0)
            {
                string missing = string.Join(", ", necessary.ToArray());
                throw new Exception("Missing parameters: " + missing);
            }

            tsg.VerifyParameters();
            tsg.PrintParameters();

            return tsg;
        }

        private void VerifyParameters()
        {
            if (NumProcs <= 0)
                throw new Exception("Invalid NumProcs parameter");
            else if (NumSets <= 0)
                throw new Exception("Invalid NumSets parameter");
            else if (NumResources < 0)
                throw new Exception("Invalid NumResources parameter");
            else if (MinC <= 0 || MaxC <= 0 || MinC > MaxC)
                throw new Exception("Invalid C parameters");
            else if (MaxTime <= 0)
                throw new Exception("Invalid MaxTime parameter");
            else if (DeadlineVariance < 0)
                throw new Exception("Invalid DeadlineVariance parameter");
            else if (UseP < 0 || ShareP < 0 || UseP > 1 || ShareP > 1)
                throw new Exception("Invalid resource probability parameter");
        }

        public SimulationData GenerateTaskSets()
        {
            SimulationData data = new SimulationData();

            for (int set = 0;  set < NumSets;  set++)
            {
                TaskSet thisSet = new TaskSet { NumProcessors = NumProcs, NumResources = NumResources };
                Hashtable startTimes = new Hashtable();
                for (int proc = 0; proc < NumProcs; proc++)
                {
                    int time = 0;
                    while (time < MaxTime)
                    {
                        int c = GenerateC();
                        int sct = time + c; // SCT = shortest completion time

                        if (sct < MaxTime)
                        {
                            int deadline = sct + rand.Next(DeadlineVariance + 1);//rand.Next(sct, (int)((1 + Laxity) * sct + 1));
                            Task task = new Task
                                {
                                    CompTime = c,
                                    Deadline = deadline,
                                    ProcessorNum = proc,
                                    ReadyTime = (time <= 0 ? 0 : rand.Next(1, time + 1))
                                };
                            startTimes.Add(task, time);
                            thisSet.Tasks.Add(task);
                            time += c;
                        }
                        else // sct >= MaxTime: Time's up on this processor
                        {
                            break;
                        }
                    }
                }
                AddResourceConstraints(thisSet, startTimes);
                data.TaskSets.Add(thisSet);
            }

            return data;
        }

        private void AddResourceConstraints(TaskSet set, Hashtable startTimes)
        {
            // Shuffle the tasks so that all tasks have good chance of getting resources
            // Without this, tasks in the top left tend to be favored
            List<Task> shuffledtasks = new List<Task>(set.Tasks);
            Shuffle(shuffledtasks);

            foreach (Task task in shuffledtasks)
            {
                // Notes:
                // The probability is the same for every resource
                // Also, a task can require multiple resources
                for (int res = 0; res < NumResources; res++)
                {
                    // TODO: Which should be favored, shared or exclusive?
                    // Exclusive is picked for now
                    if (rand.NextDouble() < UseP)
                    {
                        // Check for any lock from another task
                        if (HasLockAtTime(set, startTimes, (int)startTimes[task], res, false))
                        {
                            // Can't lock this resource, try another
                            continue;
                        }
                        task.ResourceLocks.Add(new ResourceLock { ResourceNum = res, Type = ResourceLockType.Exclusive });
                    }
                    else if (rand.NextDouble() < ShareP)
                    {
                        // Check for an exclusive lock from another task
                        if (HasLockAtTime(set, startTimes, (int)startTimes[task], res, true))
                        {
                            // Can't lock this resource, try another
                            continue;
                        }
                        task.ResourceLocks.Add(new ResourceLock { ResourceNum = res, Type = ResourceLockType.Shared });
                    }
                }
            }
        }

        private bool HasLockAtTime(TaskSet set, Hashtable startTimes, int time, int resource, bool exclusiveonly)
        {
            foreach (Task t in set.Tasks)
            {
                int startTime = (int)startTimes[t];
                int endTime = startTime + t.CompTime;
                if (startTime <= time && endTime > time)
                {
                    if (TaskHasLock(t, resource, exclusiveonly))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private bool TaskHasLock(Task task, int resource, bool exclusiveonly)
        {
            foreach (ResourceLock l in task.ResourceLocks)
            {
                if (l.ResourceNum == resource)
                {
                    if (exclusiveonly)
                    {
                        // Only exclusive locks
                        return l.Type == ResourceLockType.Exclusive;
                    }
                    else
                    {
                        // Any lock counts
                        return true;
                    }
                }
            }
            return false;
        }

        private static Random rand = new Random();

        private int GenerateC()
        {
            return rand.Next(MinC, MaxC + 1);
        }

        public static void Shuffle<T>(List<T> list)
        {
            for (int i = 0; i < list.Count; i++)
            {
                int index = rand.Next(i, list.Count);
                T t = list[index];
                list[index] = list[i];
                list[i] = t;
            }
        }

        private SimulationData HardCodedTaskSets()
        {
            SimulationData data = new SimulationData();
            {
                TaskSet set = new TaskSet { NumProcessors = 3, NumResources = 1 };
                {
                    Task task = new Task { ProcessorNum = 0, ReadyTime = 0, CompTime = 4, Deadline = 8 };
                    task.ResourceLocks.Add(new ResourceLock { ResourceNum = 0, Type = ResourceLockType.Shared });
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 1, ReadyTime = 1, CompTime = 5, Deadline = 8 };
                    task.ResourceLocks.Add(new ResourceLock { ResourceNum = 0, Type = ResourceLockType.Shared });
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 2, ReadyTime = 0, CompTime = 2, Deadline = 6 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 0, ReadyTime = 2, CompTime = 3, Deadline = 12 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 1, ReadyTime = 5, CompTime = 5, Deadline = 17 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 2, ReadyTime = 2, CompTime = 4, Deadline = 20 };
                    task.ResourceLocks.Add(new ResourceLock { ResourceNum = 0, Type = ResourceLockType.Exclusive });
                    set.Tasks.Add(task);
                }
                data.TaskSets.Add(set);
            }
            {
                TaskSet set = new TaskSet { NumProcessors = 2, NumResources = 0 };
                {
                    Task task = new Task { ProcessorNum = 0, ReadyTime = 0, CompTime = 4, Deadline = 9 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 1, ReadyTime = 1, CompTime = 5, Deadline = 6 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 0, ReadyTime = 2, CompTime = 4, Deadline = 11 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 1, ReadyTime = 3, CompTime = 4, Deadline = 10 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 0, ReadyTime = 9, CompTime = 5, Deadline = 16 };
                    set.Tasks.Add(task);
                }
                {
                    Task task = new Task { ProcessorNum = 1, ReadyTime = 7, CompTime = 5, Deadline = 17 };
                    set.Tasks.Add(task);
                }
                data.TaskSets.Add(set);
            }

            return data;
        }
    }
}
